{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ad5d052db22b9c11",
   "metadata": {},
   "source": [
    "PyTorch 是一个基于 Python 的科学计算库，主要用于深度学习研究。它提供了强大的 GPU 加速张量计算和自动微分功能。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ca6179b00695e19c",
   "metadata": {},
   "source": [
    "## 安装PyTorch\n",
    "访问PyTorch官网获取适合你环境的安装命令：https://pytorch.org/"
   ]
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T02:51:57.352515Z",
     "start_time": "2025-06-15T02:51:44.118998Z"
    }
   },
   "cell_type": "code",
   "source": "!pip3 install torch torchvision torchaudio",
   "id": "91137a246ca3b360",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Collecting torch\r\n",
      "  Using cached torch-2.7.1-cp310-none-macosx_11_0_arm64.whl.metadata (29 kB)\r\n",
      "Collecting torchvision\r\n",
      "  Using cached torchvision-0.22.1-cp310-cp310-macosx_11_0_arm64.whl.metadata (6.1 kB)\r\n",
      "Collecting torchaudio\r\n",
      "  Using cached torchaudio-2.7.1-cp310-cp310-macosx_11_0_arm64.whl.metadata (6.6 kB)\r\n",
      "Collecting filelock (from torch)\r\n",
      "  Using cached filelock-3.18.0-py3-none-any.whl.metadata (2.9 kB)\r\n",
      "Requirement already satisfied: typing-extensions>=4.10.0 in /Users/dadudu/miniconda3/envs/mini-gpt/lib/python3.10/site-packages (from torch) (4.14.0)\r\n",
      "Collecting sympy>=1.13.3 (from torch)\r\n",
      "  Using cached sympy-1.14.0-py3-none-any.whl.metadata (12 kB)\r\n",
      "Collecting networkx (from torch)\r\n",
      "  Using cached networkx-3.4.2-py3-none-any.whl.metadata (6.3 kB)\r\n",
      "Requirement already satisfied: jinja2 in /Users/dadudu/miniconda3/envs/mini-gpt/lib/python3.10/site-packages (from torch) (3.1.6)\r\n",
      "Collecting fsspec (from torch)\r\n",
      "  Using cached fsspec-2025.5.1-py3-none-any.whl.metadata (11 kB)\r\n",
      "Requirement already satisfied: numpy in /Users/dadudu/miniconda3/envs/mini-gpt/lib/python3.10/site-packages (from torchvision) (2.2.6)\r\n",
      "Requirement already satisfied: pillow!=8.3.*,>=5.3.0 in /Users/dadudu/miniconda3/envs/mini-gpt/lib/python3.10/site-packages (from torchvision) (11.2.1)\r\n",
      "Collecting mpmath<1.4,>=1.1.0 (from sympy>=1.13.3->torch)\r\n",
      "  Using cached mpmath-1.3.0-py3-none-any.whl.metadata (8.6 kB)\r\n",
      "Requirement already satisfied: MarkupSafe>=2.0 in /Users/dadudu/miniconda3/envs/mini-gpt/lib/python3.10/site-packages (from jinja2->torch) (3.0.2)\r\n",
      "Using cached torch-2.7.1-cp310-none-macosx_11_0_arm64.whl (68.6 MB)\r\n",
      "Using cached torchvision-0.22.1-cp310-cp310-macosx_11_0_arm64.whl (1.9 MB)\r\n",
      "Using cached torchaudio-2.7.1-cp310-cp310-macosx_11_0_arm64.whl (1.8 MB)\r\n",
      "Using cached sympy-1.14.0-py3-none-any.whl (6.3 MB)\r\n",
      "Using cached mpmath-1.3.0-py3-none-any.whl (536 kB)\r\n",
      "Using cached filelock-3.18.0-py3-none-any.whl (16 kB)\r\n",
      "Using cached fsspec-2025.5.1-py3-none-any.whl (199 kB)\r\n",
      "Using cached networkx-3.4.2-py3-none-any.whl (1.7 MB)\r\n",
      "Installing collected packages: mpmath, sympy, networkx, fsspec, filelock, torch, torchvision, torchaudio\r\n",
      "\u001B[2K   \u001B[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001B[0m \u001B[32m8/8\u001B[0m [torchaudio]8\u001B[0m [torchaudio]]\r\n",
      "\u001B[1A\u001B[2KSuccessfully installed filelock-3.18.0 fsspec-2025.5.1 mpmath-1.3.0 networkx-3.4.2 sympy-1.14.0 torch-2.7.1 torchaudio-2.7.1 torchvision-0.22.1\r\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "cell_type": "markdown",
   "id": "57ca8069a7255cc",
   "metadata": {},
   "source": [
    "## 创建张量"
   ]
  },
  {
   "cell_type": "code",
   "id": "2674a52f2049a17f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T03:37:55.872935Z",
     "start_time": "2025-06-15T03:37:55.846831Z"
    }
   },
   "source": [
    "import torch\n",
    "\n",
    "# 从列表创建\n",
    "x = torch.tensor([1, 2, 3])\n",
    "print(x)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1, 2, 3])\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "cell_type": "code",
   "id": "4ba1eee1b1f258f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T03:38:18.899241Z",
     "start_time": "2025-06-15T03:38:18.894939Z"
    }
   },
   "source": [
    "# 特殊张量\n",
    "zeros = torch.zeros(2, 3)  # 2行3列的全0张量\n",
    "ones = torch.ones(2, 3)  # 2行3列的全1张量\n",
    "print(zeros)\n",
    "print(ones)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0., 0., 0.],\n",
      "        [0., 0., 0.]])\n",
      "tensor([[1., 1., 1.],\n",
      "        [1., 1., 1.]])\n"
     ]
    }
   ],
   "execution_count": 8
  },
  {
   "cell_type": "code",
   "id": "a0ed57a68c18f44d",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T03:38:55.427291Z",
     "start_time": "2025-06-15T03:38:55.424398Z"
    }
   },
   "source": [
    "# 类似已有张量\n",
    "z = torch.ones_like(zeros)\n",
    "print(z)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 1., 1.],\n",
      "        [1., 1., 1.]])\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T03:49:39.871607Z",
     "start_time": "2025-06-15T03:49:39.852930Z"
    }
   },
   "cell_type": "code",
   "source": [
    "rand = torch.rand(2, 3)  # 2行3列的随机张量(0-1均匀分布)，每个数字处于0-1之间\n",
    "randn = torch.randn(2, 3)  # 2行3列的标准正态分布张量，均值为0，方差为1\n",
    "print(rand)\n",
    "print(randn)"
   ],
   "id": "11b576e48e540372",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0.1508, 0.2236, 0.3572],\n",
      "        [0.0485, 0.1831, 0.5716]])\n",
      "tensor([[-1.0323,  0.2515, -0.4873],\n",
      "        [-1.2546,  0.0312, -0.2943]])\n"
     ]
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 均值和方差\n",
    "**均值、方差**是统计学中描述数据分布特征的核心指标，以下是它们的定义和解释：\n",
    "\n",
    "---\n",
    "\n",
    "### 1. **均值（Mean）**\n",
    "- **定义**：所有数据的总和除以数据的个数。\n",
    "- **公式**（对于数据集 $x_1, x_2, \\dots, x_n$）：\n",
    "  $\n",
    "  \\text{均值} = \\frac{1}{n} \\sum_{i=1}^n x_i\n",
    "  $\n",
    "- **作用**：反映数据的平均水平。\n",
    "- **示例**：数据集 \\([2, 4, 6]\\) 的均值为 \\((2+4+6)/3 = 4\\)。\n",
    "\n",
    "---\n",
    "\n",
    "### 2. **方差（Variance）**\n",
    "- **定义**：数据与均值之差的平方的平均值，衡量数据的**离散程度**。\n",
    "- **公式**：\n",
    "  $$\n",
    "  \\text{方差} = \\frac{1}{n} \\sum_{i=1}^n (x_i - \\text{均值})^2 \\quad\n",
    "  $$\n",
    "- **作用**：方差越大，数据波动越大；方差越小，数据越集中。\n",
    "- **示例**：数据集 $[2, 4, 6]$ 的方差为 $[(2-4)^2 + (4-4)^2 + (6-4)^2]/3 = \\frac{8}{3} \\approx 2.67$。\n",
    "\n",
    "---"
   ],
   "id": "87492265fb780aa2"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": [
    "[2,4,6]\n",
    "[-10,4,18]"
   ],
   "id": "2fddd3fb3d96f8db"
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "e31c7ee8d02e75b7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAHqCAYAAADVi/1VAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABZTElEQVR4nO3deXyM9/7//+cQWcgmJJJUxFL7+m3aktZOpaiidNEtNK0uoZb6tHXa0zRtlTqn6FFFe5zQRamecrq3KLpYaldtaSlFEaokERIh798ffhlGFsmYuWYSj/vtNrebeV/XXNdr3leS6+U173m/bcYYIwAAAAAAAMBClTwdAAAAAAAAAC4/FKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlgHJs8ODBqlu3rkPb8ePHdf/99ysyMlI2m00jR470SGzuZLPZ9Oyzz7r9PMuXL5fNZtPy5cvtbZ07d1aLFi3cfm5J2r17t2w2m2bPnm3J+c5Xt25d2Ww22Ww2DRs2zPLzS1K/fv3sMVjV5wCAy1fdunU1ePBgS85VVA53uXFnnjN79mzZbDbt3r3b5ce+0IXXsuB9/fOf/3T7uSXp2Weflc1ms+RcgDtQlALcrOBG8eeffxa5vUWLFurcubPLzvfiiy9q9uzZevjhh/XWW2/pnnvucdmx3eH84kelSpUUGhqqli1baujQoVqzZo3LzjN37lxNmTLFZcdzJW+NrUOHDnrrrbeUmJhYaNusWbPUtGlT+fv7q2HDhpo6dWqpjztu3DjdfPPNqlWrVokFxlGjRumtt95SkyZNnH0LAAAP++GHHzRw4EDFxsbK399fV1xxhW644YZC940XX3xRixYt8kyQXqxz586y2Wzq06dPoW1WFz88peBDwoKHn5+fatWqpc6dO+vFF1/U4cOHXXKeEydO6Nlnn3X4MNJbeHNswKXy8XQAAJz3xhtvKD8/36Htq6++Urt27ZSSkuKhqMquTZs2euyxxyRJWVlZ+vnnn7VgwQK98cYbGjVqlCZNmuSw/8mTJ+XjU7Y/X3PnztXWrVvLNHKsY8eOOnnypHx9fct0rrIqLrbY2FidPHlSVapUcev5i1O/fn3dfffdhdpnzpyphx56SAMGDNDo0aP1zTff6NFHH9WJEyf0xBNPXPS4Tz/9tCIjI/X//t//0xdffFHsfp06dZIk/fvf/y62qAsA8F4rV65Uly5dVKdOHT3wwAOKjIzU3r17tXr1ar3yyisaPny4fd8XX3xRAwcOVL9+/TwXsBf7+OOPtX79esXFxXk6FI959NFHdc011+jMmTM6fPiwVq5cqZSUFE2aNEnvvfeeunbtat/3nnvu0R133CE/P79SH//EiRNKTU2VpDJ9YFxUPu5qJcX29NNP68knn3Tr+QF3oigFlGNFFSsOHTqkZs2auewcp0+fVn5+vlsLM1dccUWh4sdLL72kO++8U5MnT1bDhg318MMP27f5+/u7LRZJysnJka+vrypVquT2c5XEZrN59PxFOXnypJ566in17t1b77//viTpgQceUH5+vp5//nkNHTpU1atXL/EYu3btUt26dfXnn38qPDzcirABAB4wbtw4hYSEaO3atQoNDXXYdujQIc8EZZHzc4lLVadOHWVlZSk1NVUffvihC6IrmitjdocOHTpo4MCBDm2bN29Wjx49NGDAAP3000+KioqSJFWuXFmVK1d2azzZ2dmqVq2axz48LODj41PmD2sBb+Kdf3GAy1jBEOX33ntP48aNU+3ateXv769u3bppx44dDvue/x32gtft2rVLn3zyiX2Ic8F36Q8dOqSkpCTVqlVL/v7+at26tebMmeNwvPOHgU+ZMkUNGjSQn5+ffvrpJ/vXEH/55RfdfffdCgkJUXh4uP7+97/LGKO9e/eqb9++Cg4OVmRkpF5++eVL6oeAgAC99dZbCgsL07hx42SMsW+78CtfWVlZGjlypOrWrSs/Pz9FRETohhtu0IYNGySd/UTpk08+0e+//27vlwv7bd68eXr66ad1xRVXqGrVqsrMzCxyTqkC69ev13XXXaeAgADVq1dPM2bMcNhe3FwGFx6zpNiKm2vhq6++UocOHVStWjWFhoaqb9+++vnnnx32KbheO3bs0ODBgxUaGqqQkBANGTJEJ06cKN1FKMKyZct05MgRPfLIIw7tycnJys7O1ieffHLRY1zuc2gAwOVi586dat68eaGClCRFRETY/22z2ZSdna05c+bY74UFczv9/vvveuSRR9S4cWMFBASoRo0auvXWWwvdXwvuu999951Gjx6t8PBwVatWTf379y/09S5jjF544QXVrl1bVatWVZcuXfTjjz8WivGvv/7SmDFj1LJlSwUGBio4OFg9e/bU5s2bHfYrKZeQpEWLFqlFixby9/dXixYttHDhwjL1Y1BQkEaNGqWPPvrIntuU5LffftOtt96qsLAwVa1aVe3atSt0fy4p5sGDByswMFB79uzRTTfdpMDAQF1xxRWaNm2apLNfyezatauqVaum2NhYzZ0716l+c4XWrVtrypQpOnbsmF599VV7e1F52Lp165SQkKCaNWva87f77rtP0tmcq+CDstTUVPvPYUG+WdAnO3fuVK9evRQUFKS77rrLvq243Gby5MmKjY1VQECAOnXqpK1btzps79y5c5Gjss4/5sViK2pOqdOnT+v555+35/J169bV3/72N+Xm5jrsV7duXd1000369ttvde2118rf31/169fXm2++WXSHA25ASRXwUhMmTFClSpU0ZswYZWRkaOLEibrrrruKnWepadOmeuuttzRq1CjVrl3b/nW48PBwnTx5Up07d9aOHTs0bNgw1atXTwsWLNDgwYN17NgxjRgxwuFYaWlpysnJ0dChQ+Xn56ewsDD7tttvv11NmzbVhAkT9Mknn+iFF15QWFiYZs6cqa5du+qll17SO++8ozFjxuiaa65Rx44dne6DwMBA9e/fX7NmzdJPP/2k5s2bF7nfQw89pPfff1/Dhg1Ts2bNdOTIEX377bf6+eefddVVV+mpp55SRkaG9u3bp8mTJ9uPfb7nn39evr6+GjNmjHJzc0scGXb06FH16tVLt912mwYNGqT33ntPDz/8sHx9fe3JTWmVJrbzLVmyRD179lT9+vX17LPP6uTJk5o6daquv/56bdiwoVBSdNttt6levXoaP368NmzYoH//+9+KiIjQSy+9VKY4C2zcuFGSdPXVVzu0x8XFqVKlStq4cWORX/kDAFx+YmNjtWrVKm3durXEBSveeust3X///br22ms1dOhQSVKDBg0kSWvXrtXKlSt1xx13qHbt2tq9e7emT5+uzp0766efflLVqlUdjjV8+HBVr15dKSkp2r17t6ZMmaJhw4Zp/vz59n2eeeYZvfDCC+rVq5d69eqlDRs2qEePHjp16pTDsX777TctWrRIt956q+rVq6f09HTNnDlTnTp10k8//aTo6GiH/YvKJb788ksNGDBAzZo10/jx43XkyBENGTJEtWvXLlNfjhgxQpMn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iblip06dNGDAAKWmpqpmzZrq27ev0tLSCs2xVBIfH58yfe2yqOveqFEjt+aK0tlrXKlSJV155ZUO7ZGRkQoNDS30M3Dh74x09vfGFXk8UBoUpQA3K1g9rbgRJCdOnChyhbXiVgwx50347S4lFRaKisudsRZMCHnhjfV8t912m3777TdNnTpV0dHR+sc//qHmzZvrs88+K/V5XF1MKarQKJ39xNBKrr42UVFROnPmTKFVk06dOqUjR464fJ4IAEDF4Ovrq2uuuUYvvviipk+frry8PC1YsOCirxs+fLjGjRun2267Te+9956+/PJLLV68WDVq1FB+fn6h/V1533vxxRc1evRodezYUW+//ba++OILLV68WM2bNy/y3FZ8MDNixAiFhoaWOFqqLIqLubh+LE3/lrXfLlVeXp5++eWXEnNFm82m999/X6tWrdKwYcP0xx9/6L777lNcXJz9A9CLOX8EmKu4M18s7tgX8uT/OQCJohTgdrGxsZKk7du3F9p24sQJ7d27176PO2P49ddfCyUC27Ztc4jR2xw/flwLFy5UTEyM/RO+4kRFRemRRx7RokWLtGvXLtWoUcNhssvS3phLY//+/YVGJP3yyy+Szq0sV/Bp67Fjxxz2u/DTqbLEVtLP0rZt21SzZs1CI7hcrU2bNpLOrmBzvnXr1ik/P9++HQCA4hQslnHgwAF7W3H3wvfff1+JiYl6+eWXNXDgQN1www1q3759oftraRXcS3/99VeH9sOHDxcaGfL++++rS5cumjVrlu644w716NFD3bt3L/W5izuXVPS9vDQKRkv973//K3K0VGxsbLF5wvkxudOl9psz5zt58qQSEhIuum+7du00btw4rVu3Tu+8845+/PFHzZs3T5Jrc0Wp6Ov+yy+/OCxKU7169SL75cJ8sSyxxcbGKj8/v9D509PTdezYMa/N+3H5oigFuFm3bt3k6+ur6dOnFyoKvf766zp9+rR69uzp1hh69eqlgwcPOqw8c/r0aU2dOlWBgYHq1KmTW8/vjJMnT+qee+7RX3/9paeeeqrET5IuHAoeERGh6OhohyHZ1apVc9mQ8dOnT2vmzJn256dOndLMmTMVHh6uuLg4SedWDfr6668dYn399dcLHa+0sUVFRalNmzaaM2eOQwKzdetWffnll+rVq5ezb6nUunbtqrCwME2fPt2hffr06apatap69+7t9hgAAOXDsmXLihxtUTDnzflfM6tWrVqR/zmvXLlyoWNMnTrV6ZEk3bt3V5UqVTR16lSH406ZMqVU516wYIH++OOPUp3r/Pv2+ff5xYsXl+qri8UZOXKkQkND9dxzzxXa1qtXL33//fdatWqVvS07O1uvv/666tat69RcVmV1qf1WFps3b9bIkSNVvXp1JScnF7vf0aNHC8VU8EFaQb5YsJqeq4pnixYtcnjP33//vdasWeOQ9zdo0EDbtm3T4cOH7W2bN2/Wd99953CsssRWkA9e+DM9adIkSSJXg9fx8XQAQEUXERGhZ555Rk8//bQ6duyom2++WVWrVtXKlSv17rvvqkePHurTp49bYxg6dKhmzpypwYMHa/369apbt67ef/99fffdd5oyZUqJ38G3wh9//KG3335b0tnRUT/99JMWLFiggwcP6rHHHnOYVPxCWVlZql27tgYOHKjWrVsrMDBQS5Ys0dq1a/Xyyy/b94uLi9P8+fM1evRoXXPNNQoMDHS636Ojo/XSSy9p9+7datSokebPn69Nmzbp9ddfV5UqVSRJzZs3V7t27TR27Fj99ddfCgsL07x583T69OlCxytLbP/4xz/Us2dPxcfHKykpSSdPntTUqVMVEhKiZ5991qn3UxYBAQF6/vnnlZycrFtvvVUJCQn65ptv9Pbbb2vcuHEOE7AvX75cXbp0UUpKikNsb731ln7//XedOHFC0tnC3QsvvCBJuueee/gEDwAqiOHDh+vEiRPq37+/mjRpolOnTmnlypWaP3++6tatqyFDhtj3jYuL05IlSzRp0iRFR0erXr16atu2rW666Sa99dZbCgkJUbNmzbRq1SotWbJENWrUcCqm8PBwjRkzRuPHj9dNN92kXr16aePGjfrss88KzY9500036bnnntOQIUN03XXX6YcfftA777yj+vXrl/p848ePV+/evdW+fXvdd999+uuvvzR16lQ1b9681F8bu1BISIhGjBhR5Ff4nnzySb377rvq2bOnHn30UYWFhWnOnDnatWuX/vvf/7r862dFcUW/FeWbb75RTk6OffL07777Th9++KFCQkK0cOFCRUZGFvvaOXPm6LXXXlP//v3VoEEDZWVl6Y033lBwcLC9iBMQEKBmzZpp/vz5atSokcLCwtSiRQu1aNHCqXivvPJKtW/fXg8//LByc3M1ZcoU1ahRQ48//rh9n/vuu0+TJk1SQkKCkpKSdOjQIc2YMUPNmzd3WJSmLLG1bt1aiYmJev3113Xs2DF16tRJ33//vebMmaN+/fqpS5cuTr0fwG08suYfcBl6++23Tbt27Uy1atWMn5+fadKkiUlNTTU5OTkO+xW37G1Ry+ZeuFysMWeXdu3du3eh86enp5shQ4aYmjVrGl9fX9OyZctCS/AWtbRwgYLlZg8fPuzQnpiYaKpVq1Zo//OXMC5JwVK0kozNZjPBwcGmefPm5oEHHjBr1qwp8jU6b0nc3Nxc83//93+mdevWJigoyFSrVs20bt3avPbaaw6vOX78uLnzzjtNaGiowzLMJS0zXLBt2bJlhd7XunXrTHx8vPH39zexsbHm1VdfLfT6nTt3mu7duxs/Pz9Tq1Yt87e//c0sXry40DGLi624pZKXLFlirr/+ehMQEGCCg4NNnz59zE8//eSwT3HX68IlkosTGxtb5NLcBV5//XXTuHFj4+vraxo0aGAmT57ssLS2McZ89NFHRpKZMWOGQ3unTp3s1/zCx/n9cv7+pflZAgB4l88++8zcd999pkmTJiYwMND4+vqaK6+80gwfPtykp6c77Ltt2zbTsWNHExAQYCTZ70FHjx615y+BgYEmISHBbNu2rdB9quD+tnbtWofjFnUvP3PmjElNTTVRUVEmICDAdO7c2WzdurXQMXNycsxjjz1m3+/66683q1atMp06dTKdOnUqdI6icgljjPnvf/9rmjZtavz8/EyzZs3MBx98UGQOV5Ti7oFHjx41ISEhReZtO3fuNAMHDjShoaHG39/fXHvttebjjz8usl+Kirmsud2FuWdp+624POdCBbEWPKpUqWLCw8NNx44dzbhx48yhQ4cKvebCfGfDhg1m0KBBpk6dOsbPz89ERESYm266yaxbt87hdStXrjRxcXHG19fXId8srk8Ktp1/Lc/Pp19++WUTExNj/Pz8TIcOHczmzZsLvf7tt9829evXN76+vqZNmzbmiy++KPLno7jYCnK+8+Xl5ZnU1FRTr149U6VKFRMTE2PGjh1b6P8dxf2/4cJrBbiTzRhmMAMAOKpbt67i4+M1depUBQQEODVX1eOPP653331XO3bskJ+fX5lfn5WVpdzcXPXt21cZGRn2Se8BAAAAVAzMKQUAKNK8efMUHh6uJ554wqnXL1u2TH//+9+dKkhJZ7/KFx4erpUrVzr1egAAAADejZFSAIBCvvvuO508eVKSFBMT4zAZrVW2bNmiQ4cOSZICAwPVrl07y2MAAAAA4D4UpQAAAAAAAGA5vr4HAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByPp4OwN3y8/O1f/9+BQUFyWazeTocAABQzhhjlJWVpejoaFWqdHl8nkf+BAAALkVp86cKX5Tav3+/YmJiPB0GAAAo5/bu3avatWt7OgxLkD8BAABXuFj+VOGLUkFBQZLOdkRwcLCHowEAAOVNZmamYmJi7DnF5YD8CQAAXIrS5k8VvihVMOQ8ODiYpAoAADjtcvoaG/kTAABwhYvlT5fHxAgAAAAAAADwKhSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByPp4OAADcyWa7+D7GuD8OAACA89lSS5GklIFJIaEBUP4wUgoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOVbfAwAAAAAXcvXKegBQUTFSCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwnI+nA4BzbKVcZdYY98YBAAAAAADgDEZKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAs5+PpAOB5Nlvp9jPGvXFURPQtUDql+V3h98R9+FsFAAAAT2CkFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFjOo0Wp8ePH65prrlFQUJAiIiLUr18/bd++3WGfzp07y2azOTweeughD0UMAAAAAAAAV/BoUWrFihVKTk7W6tWrtXjxYuXl5alHjx7Kzs522O+BBx7QgQMH7I+JEyd6KGIAAAAAAAC4go8nT/755587PJ89e7YiIiK0fv16dezY0d5etWpVRUZGWh0eAAAAAAAA3MSr5pTKyMiQJIWFhTm0v/POO6pZs6ZatGihsWPH6sSJE54IDwAAAAAAAC7i0ZFS58vPz9fIkSN1/fXXq0WLFvb2O++8U7GxsYqOjtaWLVv0xBNPaPv27frggw+KPE5ubq5yc3PtzzMzM90eOwAAAADvZEu1lWo/k2LcHAkA4EJeU5RKTk7W1q1b9e233zq0Dx061P7vli1bKioqSt26ddPOnTvVoEGDQscZP368UlNT3R4vLo2tdLmBDLmBnav7rDTH8+b+L21/oOLg7wYAAABQsXjF1/eGDRumjz/+WMuWLVPt2rVL3Ldt27aSpB07dhS5fezYscrIyLA/9u7d6/J4AQAAPOXZZ58ttDJxkyZN7NtzcnKUnJysGjVqKDAwUAMGDFB6eroHIwYAACiaR0dKGWM0fPhwLVy4UMuXL1e9evUu+ppNmzZJkqKioorc7ufnJz8/P1eGCQAA4FWaN2+uJUuW2J/7+JxL6UaNGqVPPvlECxYsUEhIiIYNG6ZbbrlF3333nSdCBQAAKJZHi1LJycmaO3eu/ve//ykoKEgHDx6UJIWEhCggIEA7d+7U3Llz1atXL9WoUUNbtmzRqFGj1LFjR7Vq1cqToQMAAHiMj49PkSsTZ2RkaNasWZo7d666du0qSUpLS1PTpk21evVqtWvXzupQAQAAiuXRr+9Nnz5dGRkZ6ty5s6KiouyP+fPnS5J8fX21ZMkS9ejRQ02aNNFjjz2mAQMG6KOPPvJk2AAAAB7166+/Kjo6WvXr19ddd92lPXv2SJLWr1+vvLw8de/e3b5vkyZNVKdOHa1atcpT4QIAABTJ41/fK0lMTIxWrFhhUTQAAADer23btpo9e7YaN26sAwcOKDU1VR06dNDWrVt18OBB+fr6KjQ01OE1tWrVso9ILwqrFwMAAE/wmtX3AAAAcHE9e/a0/7tVq1Zq27atYmNj9d577ykgIMCpY7J6MQAA8ASvWH0PAAAAzgkNDVWjRo20Y8cORUZG6tSpUzp27JjDPunp6UXOQVWA1YsBAIAnUJQCAAAox44fP66dO3cqKipKcXFxqlKlipYuXWrfvn37du3Zs0fx8fHFHsPPz0/BwcEODwAAAHfj63sAAADlyJgxY9SnTx/FxsZq//79SklJUeXKlTVo0CCFhIQoKSlJo0ePVlhYmIKDgzV8+HDFx8ez8h4AAPA6FKUAAADKkX379mnQoEE6cuSIwsPD1b59e61evVrh4eGSpMmTJ6tSpUoaMGCAcnNzlZCQoNdee83DUQMAABRGUQoAAKAcmTdvXonb/f39NW3aNE2bNs2iiAAAAJzDnFIAAAAAAACwHEUpAAAAAAAAWI6v77mAzea6YxnjumNdLkrb//QtAFya0vy95W8tAAAASouRUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJZjonMAAAAAlz1b6sVXczAprOZwPvoMwKVipBQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDkfTwcAAAAAALi82VJtpdrPpBg3RwLAShSlAACAy9hK938KGf5PAQAAcNnj63sAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy7H6HgAAAACUc7bUiy9/alJY+hSAd2GkFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsJyPpwNAxWOzeToCWK2019wY98aBkl0u1+lyeZ8AAABAecdIKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJbz8XQAAAAAAAD3s6XaSrWfSTFujgQAzmKkFAAAAAAAACzHSCkAAIphK90HyjIu/EDZE+cEAAAAPIGRUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5jxalxo8fr2uuuUZBQUGKiIhQv379tH37dod9cnJylJycrBo1aigwMFADBgxQenq6hyIGAAAAAACAK3i0KLVixQolJydr9erVWrx4sfLy8tSjRw9lZ2fb9xk1apQ++ugjLViwQCtWrND+/ft1yy23eDBqAAAAAAAAXCofT578888/d3g+e/ZsRUREaP369erYsaMyMjI0a9YszZ07V127dpUkpaWlqWnTplq9erXatWvnibABAAAAAABwiTxalLpQRkaGJCksLEyStH79euXl5al79+72fZo0aaI6depo1apVFKUAAACAy5Qt1XZZnBMAKjKvKUrl5+dr5MiRuv7669WiRQtJ0sGDB+Xr66vQ0FCHfWvVqqWDBw8WeZzc3Fzl5uban2dmZrotZgAAAAAAADjHa1bfS05O1tatWzVv3rxLOs748eMVEhJif8TExLgoQsB9bLaLPwC4Fr93qCgmTJggm82mkSNH2ttYKAYAAJQHXlGUGjZsmD7++GMtW7ZMtWvXtrdHRkbq1KlTOnbsmMP+6enpioyMLPJYY8eOVUZGhv2xd+9ed4YOAADgMWvXrtXMmTPVqlUrh3YWigEAAOWBR4tSxhgNGzZMCxcu1FdffaV69eo5bI+Li1OVKlW0dOlSe9v27du1Z88excfHF3lMPz8/BQcHOzwAAAAqmuPHj+uuu+7SG2+8oerVq9vbCxaKmTRpkrp27aq4uDilpaVp5cqVWr16tQcjBgAAcOTRolRycrLefvttzZ07V0FBQTp48KAOHjyokydPSpJCQkKUlJSk0aNHa9myZVq/fr2GDBmi+Ph4JjkHAACXteTkZPXu3dthQRjp4gvFFCU3N1eZmZkODwAAAHfz6ETn06dPlyR17tzZoT0tLU2DBw+WJE2ePFmVKlXSgAEDlJubq4SEBL322msWRwoAAOA95s2bpw0bNmjt2rWFtjmzUMz48eOVmprqjlABAACK5dGilDHmovv4+/tr2rRpmjZtmgURAQAAeLe9e/dqxIgRWrx4sfz9/V1yzLFjx2r06NH255mZmSwWAwAA3M4rJjoHAABA6axfv16HDh3SVVddJR8fH/n4+GjFihX617/+JR8fH9WqVavMC8UwJycAAPAEj46UAgAAQNl069ZNP/zwg0PbkCFD1KRJEz3xxBOKiYmxLxQzYMAASRdfKAYAAMATKEoBAACUI0FBQWrRooVDW7Vq1VSjRg17e8FCMWFhYQoODtbw4cNZKAYAAHgdilIAAAAVDAvFAACA8oCiFAAAQDm3fPlyh+csFAMAAMoDJjoHAAAAAACA5ShKAQAAAAAAwHJ8fc/L2GyejgCluQbGuD8OVGyl/V3nZw0AAABARcVIKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJbz8XQAAAAAAACUhi3VdtF9TIqxIBIArsBIKQAAAAAAAFiOkVIVnO3iHyQAlinNz6Px4g+2+H2CNynvv08AAAAAI6UAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOOaUAAAAAWKI0K6dJrJ4GAJcLRkoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJbz8XQAAAAAAICKyZZq83QIALyYUyOlfvvtN1fHAQAAUOGRQwEAAJzjVFHqyiuvVJcuXfT2228rJyfH1TEBAABUSORQAAAA5zhVlNqwYYNatWql0aNHKzIyUg8++KC+//57V8cGL2Ozle7hrcp7/EBxPPGzXdpzXi6/d5fL+8SlI4cCAAA4x6miVJs2bfTKK69o//79+s9//qMDBw6offv2atGihSZNmqTDhw+7Ok4AAIByjxwKAADgnEtafc/Hx0e33HKLFixYoJdeekk7duzQmDFjFBMTo3vvvVcHDhxwVZwAAAAVBjkUAADAJRal1q1bp0ceeURRUVGaNGmSxowZo507d2rx4sXav3+/+vbt66o4AQAAKgxyKAAAAMnHmRdNmjRJaWlp2r59u3r16qU333xTvXr1UqVKZ2tc9erV0+zZs1W3bl1XxgoAAFCukUMBpWNLZSI+T6L/AVjFqaLU9OnTdd9992nw4MGKiooqcp+IiAjNmjXrkoIDAACoSMihAAAAznGqKPXrr79edB9fX18lJiY6c3gAAIAKiRwKAADgHKfmlEpLS9OCBQsKtS9YsEBz5sy55KAAAAAqInIoAACAc5wqSo0fP141a9Ys1B4REaEXX3zxkoMCAACoiMihAAAAznGqKLVnzx7Vq1evUHtsbKz27NlzyUEBAABURORQAAAA5zhVlIqIiNCWLVsKtW/evFk1atS45KAAAAAqInIoAACAc5wqSg0aNEiPPvqoli1bpjNnzujMmTP66quvNGLECN1xxx2ujhEAAKBCIIcCAAA4x6nV955//nnt3r1b3bp1k4/P2UPk5+fr3nvvZT4EAACAYpBDAQAAnGMzxhhnX/zLL79o8+bNCggIUMuWLRUbG+vK2FwiMzNTISEhysjIUHBwsFvOYbO55bCQVJqfTk/0f2l/a8p7bJ54n5fLOUvLE78D3vp7VxFY3bfl/WfbW7gjl/D2HMqK/AmXJ1sqNxC4n0kpRzcZoIIqbS7h1EipAo0aNVKjRo0u5RBAifiPb9nRZxUL1xOXwpt/fkobW3kqXpUFORQAAICTRakzZ85o9uzZWrp0qQ4dOqT8/HyH7V999ZVLggMAAKhIyKEAAADOcaooNWLECM2ePVu9e/dWixYtZPPmj2IBAAC8BDkUAADAOU4VpebNm6f33ntPvXr1cnU8AAAAFRY5FAAAwDlOFaV8fX115ZVXujoWAACACo0cCgDcr7QT6jMhOuB5lZx50WOPPaZXXnlFl7BwHwAAwGWHHAoAAOAcp0ZKffvtt1q2bJk+++wzNW/eXFWqVHHY/sEHH7gkOAAAgIqEHAoAAOAcp4pSoaGh6t+/v6tjAQAAqNDIoQAAAM5xqiiVlpbm6jgAAAAqPHIoAACAc5yaU0qSTp8+rSVLlmjmzJnKysqSJO3fv1/Hjx8v9TG+/vpr9enTR9HR0bLZbFq0aJHD9sGDB8tmszk8brzxRmdDBgAA8DhX5FAAAAAVgVMjpX7//XfdeOON2rNnj3Jzc3XDDTcoKChIL730knJzczVjxoxSHSc7O1utW7fWfffdp1tuuaXIfW688UaHTxX9/PycCRkAAMDjXJVDAd6mtKudAQBwPqeKUiNGjNDVV1+tzZs3q0aNGvb2/v3764EHHij1cXr27KmePXuWuI+fn58iIyOdCRMAAMCruCqHAgAAqAicKkp98803WrlypXx9fR3a69atqz/++MMlgRVYvny5IiIiVL16dXXt2lUvvPCCQxJ3odzcXOXm5tqfZ2ZmujQeAAAAZ1mZQwEAAHg7p+aUys/P15kzZwq179u3T0FBQZccVIEbb7xRb775ppYuXaqXXnpJK1asUM+ePYs8d4Hx48crJCTE/oiJiXFZPEABm610j/LucnmfAGAVV+RQ06dPV6tWrRQcHKzg4GDFx8frs88+s2/PyclRcnKyatSoocDAQA0YMEDp6ekuew8AAACu4lRRqkePHpoyZYr9uc1m0/Hjx5WSkqJevXq5Kjbdcccduvnmm9WyZUv169dPH3/8sdauXavly5cX+5qxY8cqIyPD/ti7d6/L4gEAALgUrsihateurQkTJmj9+vVat26dunbtqr59++rHH3+UJI0aNUofffSRFixYoBUrVmj//v3Fzt0JAADgSTZjjCnri/bt26eEhAQZY/Trr7/q6quv1q+//qqaNWvq66+/VkRERNkDsdm0cOFC9evXr8T9wsPD9cILL+jBBx8s1XEzMzMVEhKijIwMBQcHlzmu0mCkCOA6pf2L5MrfO0+c05uVpj8ul75wNfq27MqepbieK3MJd+RQkhQWFqZ//OMfGjhwoMLDwzV37lwNHDhQkrRt2zY1bdpUq1atUrt27Up1PCvyJ1QsTHSO8sikeMFNBqigSptLODWnVO3atbV582bNmzdPW7Zs0fHjx5WUlKS77rpLAQEBTgd9Mfv27dORI0cUFRXltnMAAAC4i6tzqDNnzmjBggXKzs5WfHy81q9fr7y8PHXv3t2+T5MmTVSnTp0Si1LMyQkAADzBqaKUJPn4+Ojuu+++pJMfP35cO3bssD/ftWuXNm3apLCwMIWFhSk1NVUDBgxQZGSkdu7cqccff1xXXnmlEhISLum8AAAAnuKKHOqHH35QfHy8cnJyFBgYqIULF6pZs2batGmTfH19FRoa6rB/rVq1dPDgwWKPN378eKWmpl5STAAAAGXlVFHqzTffLHH7vffeW6rjrFu3Tl26dLE/Hz16tCQpMTFR06dP15YtWzRnzhwdO3ZM0dHR6tGjh55//nn5+fk5EzYAAIBHuSqHaty4sTZt2qSMjAy9//77SkxM1IoVK5yOa+zYsfY8TDo7UorFYgAAgLs5NadU9erVHZ7n5eXpxIkT8vX1VdWqVfXXX3+5LMBLxZxSQPnCnFKex7xH7kPfll1Fm1PKXTlU9+7d1aBBA91+++3q1q2bjh496jBaKjY2ViNHjtSoUaNKdTzmlEJZMacUyiPmlALcp7S5hFOr7x09etThcfz4cW3fvl3t27fXu+++63TQAAAAFZm7cqj8/Hzl5uYqLi5OVapU0dKlS+3btm/frj179ig+Pt4VbwEAAMBlnJ5T6kINGzbUhAkTdPfdd2vbtm2uOiwAAECFVtYcauzYserZs6fq1KmjrKwszZ07V8uXL9cXX3yhkJAQJSUlafTo0QoLC1NwcLCGDx+u+Pj4Uq+8BwAAYBWXFaWksxN37t+/35WHBAAAqPDKkkMdOnRI9957rw4cOKCQkBC1atVKX3zxhW644QZJ0uTJk1WpUiUNGDBAubm5SkhI0GuvvebO8AEAAJziVFHqww8/dHhujNGBAwf06quv6vrrr3dJYAAAABWNK3KoWbNmlbjd399f06ZN07Rp05yOEwAAwApOFaX69evn8Nxmsyk8PFxdu3bVyy+/7Iq4AAAAKhxyKAAAgHOcKkrl5+e7Og4AAIAKjxwKAADgHKdW3wMAAAAAAAAuhVMjpUaPHl3qfSdNmuTMKQAAHmKzeTqCiou+LbvS9Jkx7o/DVcihAAAAznGqKLVx40Zt3LhReXl5aty4sSTpl19+UeXKlXXVVVfZ97ORfQMAANiRQwEAAJzjVFGqT58+CgoK0pw5c1S9enVJ0tGjRzVkyBB16NBBjz32mEuDBAAAqAjIoQAAAM6xGVP2Qe9XXHGFvvzySzVv3tyhfevWrerRo4f279/vsgAvVWZmpkJCQpSRkaHg4GC3nIMPMwHXKe1fJFf+3nninABcx91f33NlLlFecigr8idULLZUbpIof0xKOfr+N1DOlDaXcGqi88zMTB0+fLhQ++HDh5WVleXMIQEAACo8cigAAIBznCpK9e/fX0OGDNEHH3ygffv2ad++ffrvf/+rpKQk3XLLLa6OEQAAoEIghwIAADjHqTmlZsyYoTFjxujOO+9UXl7e2QP5+CgpKUn/+Mc/XBogAABARUEOBQAAcI5Tc0oVyM7O1s6dOyVJDRo0ULVq1VwWmKswpxRQvjCnFICyKk9zShXw9hyKOaVQVswphfKIOaUA93HrnFIFDhw4oAMHDqhhw4aqVq2aLqG+BQAAcNkghwIAAHCyKHXkyBF169ZNjRo1Uq9evXTgwAFJUlJSEksZAwAAFIMcCgAA4BynilKjRo1SlSpVtGfPHlWtWtXefvvtt+vzzz93WXAAAAAVCTkUAADAOU5NdP7ll1/qiy++UO3atR3aGzZsqN9//90lgQEAAFQ05FAAAADnODVSKjs72+HTvQJ//fWX/Pz8LjkoAACAiogcCgAA4BynilIdOnTQm2++aX9us9mUn5+viRMnqkuXLi4LDgAAoCIhhwIAADjHqa/vTZw4Ud26ddO6det06tQpPf744/rxxx/1119/6bvvvnN1jAAuIzYPrCjtiXMCuDyRQwEAAJzj1EipFi1a6JdfflH79u3Vt29fZWdn65ZbbtHGjRvVoEEDV8cIAABQIZBDAQAAnFPmkVJ5eXm68cYbNWPGDD311FPuiAkAAKDCIYcCAABwVOaRUlWqVNGWLVvcEQsAAECFRQ4FAADgyKmv7919992aNWuWq2MBAACo0MihAAAAznFqovPTp0/rP//5j5YsWaK4uDhVq1bNYfukSZNcEhwAAEBFQg4FAABwTpmKUr/99pvq1q2rrVu36qqrrpIk/fLLLw772FjGCgAAwAE5FAAAQGFlKko1bNhQBw4c0LJlyyRJt99+u/71r3+pVq1abgkOAACgIiCHAgAAKKxMc0oZYxyef/bZZ8rOznZpQAAAABUNORQAAEBhTk10XuDCBAsAAAAXRw4FAABQxqKUzWYrNN8B8x8AAACUjBwKAACgsDLNKWWM0eDBg+Xn5ydJysnJ0UMPPVRo5ZgPPvjAdRECAACUc+RQAAAAhZWpKJWYmOjw/O6773ZpMAAAABURORQAAEBhZSpKpaWluSsOAACACoscCgAAoLBLmugcAAAAAAAAcAZFKQAAAAAAAFiOohQAAAAAAAAsV6Y5pQAAAABUDLZUW6n2MynGzZEAAC5XjJQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI7V9wAAAAAUq7Sr9AG4OFa9BBwxUgoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsJyPpwMAAAAAAMBqtlRbqfYzKcbNkQCXL0ZKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACW82hR6uuvv1afPn0UHR0tm82mRYsWOWw3xuiZZ55RVFSUAgIC1L17d/3666+eCRYAAAAAAAAu49GiVHZ2tlq3bq1p06YVuX3ixIn617/+pRkzZmjNmjWqVq2aEhISlJOTY3GkAAAAAAAAcCWPFqV69uypF154Qf379y+0zRijKVOm6Omnn1bfvn3VqlUrvfnmm9q/f3+hEVUAAACXi/Hjx+uaa65RUFCQIiIi1K9fP23fvt1hn5ycHCUnJ6tGjRoKDAzUgAEDlJ6e7qGIAQAAiua1c0rt2rVLBw8eVPfu3e1tISEhatu2rVatWlXs63Jzc5WZmenwAAAAqChWrFih5ORkrV69WosXL1ZeXp569Oih7Oxs+z6jRo3SRx99pAULFmjFihXav3+/brnlFg9GDQAAUJiPpwMozsGDByVJtWrVcmivVauWfVtRxo8fr9TUVLfGBgAA4Cmff/65w/PZs2crIiJC69evV8eOHZWRkaFZs2Zp7ty56tq1qyQpLS1NTZs21erVq9WuXTtPhA0AAFCI146UctbYsWOVkZFhf+zdu9fTIQEAALhNRkaGJCksLEyStH79euXl5TmMNm/SpInq1KlT4mhzAAAAq3ntSKnIyEhJUnp6uqKiouzt6enpatOmTbGv8/Pzk5+fn7vDAwAA8Lj8/HyNHDlS119/vVq0aCHp7GhzX19fhYaGOuxb0mjz3Nxc5ebm2p8z/QEAALCC1xal6tWrp8jISC1dutRehMrMzNSaNWv08MMPezY4AAAAL5CcnKytW7fq22+/vaTjMP0BAFwaW6rN0yEA5ZJHv753/Phxbdq0SZs2bZJ0dnLzTZs2ac+ePbLZbBo5cqReeOEFffjhh/rhhx907733Kjo6Wv369fNk2AAAAB43bNgwffzxx1q2bJlq165tb4+MjNSpU6d07Ngxh/3T09PtI9EvxPQHAADAEzw6UmrdunXq0qWL/fno0aMlSYmJiZo9e7Yef/xxZWdna+jQoTp27Jjat2+vzz//XP7+/p4KGQAAwKOMMRo+fLgWLlyo5cuXq169eg7b4+LiVKVKFS1dulQDBgyQJG3fvl179uxRfHx8kcdk+gMAAOAJHi1Kde7cWcaYYrfbbDY999xzeu655yyMCgAAwHslJydr7ty5+t///qegoCD7PFEhISEKCAhQSEiIkpKSNHr0aIWFhSk4OFjDhw9XfHw8K+8BAACv4rVzSgEAAKCw6dOnSzr74d750tLSNHjwYEnS5MmTValSJQ0YMEC5ublKSEjQa6+9ZnGkAAAAJaMoBQAAUI6UNMq8gL+/v6ZNm6Zp06ZZEBEAAIBzPDrROQAAAAAAAC5PFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJbz8XQAAAAAAAB4K1uqzdMhABUWI6UAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALOfj6QAAAAAAAMA5tlTbRfcxKcaCSAD3YqQUAAAAAAAALEdRCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJajKAUAAAAAAADLeXVR6tlnn5XNZnN4NGnSxNNhAQAAAAAA4BL5eDqAi2nevLmWLFlif+7j4/UhAwAAAAAA4CK8eqSUdLYIFRkZaX/UrFnT0yEBAAB41Ndff60+ffooOjpaNptNixYtcthujNEzzzyjqKgoBQQEqHv37vr11189EywAAEAxvL4o9euvvyo6Olr169fXXXfdpT179ng6JAAAAI/Kzs5W69atNW3atCK3T5w4Uf/61780Y8YMrVmzRtWqVVNCQoJycnIsjhQAAKB4Xv1duLZt22r27Nlq3LixDhw4oNTUVHXo0EFbt25VUFBQka/Jzc1Vbm6u/XlmZqZV4QIAAFiiZ8+e6tmzZ5HbjDGaMmWKnn76afXt21eS9Oabb6pWrVpatGiR7rjjDitDBQAAKJZXj5Tq2bOnbr31VrVq1UoJCQn69NNPdezYMb333nvFvmb8+PEKCQmxP2JiYiyMGAAAwLN27dqlgwcPqnv37va2kJAQtW3bVqtWrSryNbm5ucrMzHR4AAAAuJtXj5S6UGhoqBo1aqQdO3YUu8/YsWM1evRo+/PMzEwKUwAA4LJx8OBBSVKtWrUc2mvVqmXfdqHx48crNTXV7bGheLZUW6n2MynGZccCcHkozd+E0vxtAdzBq0dKXej48ePauXOnoqKiit3Hz89PwcHBDg8AAAAUb+zYscrIyLA/9u7d6+mQAADAZcCri1JjxozRihUrtHv3bq1cuVL9+/dX5cqVNWjQIE+HBgAA4JUiIyMlSenp6Q7t6enp9m0X4kM9AADgCV5dlNq3b58GDRqkxo0b67bbblONGjW0evVqhYeHezo0AAAAr1SvXj1FRkZq6dKl9rbMzEytWbNG8fHxHowMAADAkVfPKTVv3jxPhwAAAOB1jh8/7jDH5q5du7Rp0yaFhYWpTp06GjlypF544QU1bNhQ9erV09///ndFR0erX79+ngsaAADgAl5dlAIAAEBh69atU5cuXezPCxZ5SUxM1OzZs/X4448rOztbQ4cO1bFjx9S+fXt9/vnn8vf391TIAAAAhVCUAgAAKGc6d+4sY4pfKclms+m5557Tc889Z2FUAAAAZUNRCgAAACgnSrO0O4DLA38PUBF49UTnAAAAAAAAqJgoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlqMoBQAAAAAAAMtRlAIAAAAAAIDlKEoBAAAAAADAchSlAAAAAAAAYDmKUgAAAAAAALAcRSkAAAAAAABYjqIUAAAAAAAALEdRCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByPp4OAAAAACiPbKk2T4cAAEC5xkgpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByPp4OAAAAAAAAeD9bqq1U+5kU4+ZIUFEwUgoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOVbfAwAAAADgMlbaVfU8gRX/KjZGSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKAAAAAAAAlvPxdAAAAABAcVy5FLg3L3kOABVJaf7elubvtqe48t6DkjFSCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwnI+nAwAAAMDlqbRLbgMAKh5X3wNKczyTYlx6Tm9V2r71hv5gpBQAAAAAAAAsVy6KUtOmTVPdunXl7++vtm3b6vvvv/d0SAAAAF6PHAoAAHgzry9KzZ8/X6NHj1ZKSoo2bNig1q1bKyEhQYcOHfJ0aAAAAF6LHAoAAHg7ry9KTZo0SQ888ICGDBmiZs2aacaMGapatar+85//eDo0AAAAr0UOBQAAvJ1XF6VOnTql9evXq3v37va2SpUqqXv37lq1apUHIwMAAPBe5FAAAKA88OrV9/7880+dOXNGtWrVcmivVauWtm3bVuRrcnNzlZuba3+ekZEhScrMzHRfoAAAwGPcfYsvyCGM8fwKNaVV1hzKY/lTjusOVapYXXg+AED5U+r7WinvF15bZ/CC+EubP3l1UcoZ48ePV2pqaqH2mJgYD0QDAADcLSTEmvNkZWUpxKqTWawi5E8hEyrmtQEAuI6r7xXl/d5jRfwXy5+8uihVs2ZNVa5cWenp6Q7t6enpioyMLPI1Y8eO1ejRo+3P8/Pz9ddff6lGjRqy2WwujzEzM1MxMTHau3evgoODXX58XBzXwLPof8/jGngW/e957r4GxhhlZWUpOjra5cd2l7LmUO7Mn/gdKR59Uzz6pnj0TfHom+LRNyWjf4rnbN+UNn/y6qKUr6+v4uLitHTpUvXr10/S2SRp6dKlGjZsWJGv8fPzk5+fn0NbaGiomyOVgoOD+eH1MK6BZ9H/nsc18Cz63/PceQ3K2wipsuZQVuRP/I4Uj74pHn1TPPqmePRN8eibktE/xXOmb0qTP3l1UUqSRo8ercTERF199dW69tprNWXKFGVnZ2vIkCGeDg0AAMBrkUMBAABv5/VFqdtvv12HDx/WM888o4MHD6pNmzb6/PPPC03cCQAAgHPIoQAAgLfz+qKUJA0bNqzYr+t5mp+fn1JSUgoNeYd1uAaeRf97HtfAs+h/z+MaFM8bciiuT/Hom+LRN8Wjb4pH3xSPvikZ/VM8d/eNzZSn9Y0BAAAAAABQIVTydAAAAAAAAAC4/FCUAgAAAAAAgOUoSgEAAAAAAMByFKVKYdq0aapbt678/f3Vtm1bff/99yXuv2DBAjVp0kT+/v5q2bKlPv30U4sirbjKcg3eeOMNdejQQdWrV1f16tXVvXv3i14zlKysvwMF5s2bJ5vNpn79+rk3wMtAWa/BsWPHlJycrKioKPn5+alRo0b8LboEZe3/KVOmqHHjxgoICFBMTIxGjRqlnJwci6KteL7++mv16dNH0dHRstlsWrRo0UVfs3z5cl111VXy8/PTlVdeqdmzZ7s9TlzczTffrDp16sjf319RUVG65557tH//fk+H5XG7d+9WUlKS6tWrp4CAADVo0EApKSk6deqUp0PzCuPGjdN1112nqlWrKjQ01NPheJyzeVlF5sx94nIxfvx4XXPNNQoKClJERIT69eun7du3ezosrzB9+nS1atVKwcHBCg4OVnx8vD777DNPh+WVJkyYIJvNppEjR7r82BSlLmL+/PkaPXq0UlJStGHDBrVu3VoJCQk6dOhQkfuvXLlSgwYNUlJSkjZu3Kh+/fqpX79+2rp1q8WRVxxlvQbLly/XoEGDtGzZMq1atUoxMTHq0aOH/vjjD4sjrxjK2v8Fdu/erTFjxqhDhw4WRVpxlfUanDp1SjfccIN2796t999/X9u3b9cbb7yhK664wuLIK4ay9v/cuXP15JNPKiUlRT///LNmzZql+fPn629/+5vFkVcc2dnZat26taZNm1aq/Xft2qXevXurS5cu2rRpk0aOHKn7779fX3zxhZsjxcV06dJF7733nrZv367//ve/2rlzpwYOHOjpsDxu27Ztys/P18yZM/Xjjz9q8uTJmjFjBn83/n+nTp3SrbfeqocfftjToXics3lZRVfW+8TlZMWKFUpOTtbq1au1ePFi5eXlqUePHsrOzvZ0aB5Xu3ZtTZgwQevXr9e6devUtWtX9e3bVz/++KOnQ/Mqa9eu1cyZM9WqVSv3nMCgRNdee61JTk62Pz9z5oyJjo4248ePL3L/2267zfTu3duhrW3btubBBx90a5wVWVmvwYVOnz5tgoKCzJw5c9wVYoXmTP+fPn3aXHfddebf//63SUxMNH379rUg0oqrrNdg+vTppn79+ubUqVNWhVihlbX/k5OTTdeuXR3aRo8eba6//nq3xnm5kGQWLlxY4j6PP/64ad68uUPb7bffbhISEtwYGZzxv//9z9hsNv5eFWHixImmXr16ng7Dq6SlpZmQkBBPh+FRl5oXXw5Kc5+4nB06dMhIMitWrPB0KF6pevXq5t///renw/AaWVlZpmHDhmbx4sWmU6dOZsSIES4/ByOlSnDq1CmtX79e3bt3t7dVqlRJ3bt316pVq4p8zapVqxz2l6SEhIRi90fJnLkGFzpx4oTy8vIUFhbmrjArLGf7/7nnnlNERISSkpKsCLNCc+YafPjhh4qPj1dycrJq1aqlFi1a6MUXX9SZM2esCrvCcKb/r7vuOq1fv97+dYrffvtNn376qXr16mVJzOBeXF789ddfeuedd3TdddepSpUqng7H62RkZJC7wIEr8mIgIyNDkvj7coEzZ85o3rx5ys7OVnx8vKfD8RrJycnq3bt3obzKlXzcduQK4M8//9SZM2dUq1Yth/ZatWpp27ZtRb7m4MGDRe5/8OBBt8VZkTlzDS70xBNPKDo62q2/SBWVM/3/7bffatasWdq0aZMFEVZ8zlyD3377TV999ZXuuusuffrpp9qxY4ceeeQR5eXlKSUlxYqwKwxn+v/OO+/Un3/+qfbt28sYo9OnT+uhhx7iazgWKu5enJmZqZMnTyogIMBDkUE6e19+9dVXdeLECbVr104ff/yxp0PyOjt27NDUqVP1z3/+09OhwIu4Ii/G5S0/P18jR47U9ddfrxYtWng6HK/www8/KD4+Xjk5OQoMDNTChQvVrFkzT4flFebNm6cNGzZo7dq1bj0PI6VQoU2YMEHz5s3TwoUL5e/v7+lwKrysrCzdc889euONN1SzZk1Ph3PZys/PV0REhF5//XXFxcXp9ttv11NPPaUZM2Z4OrTLwvLly/Xiiy/qtdde04YNG/TBBx/ok08+0fPPP+/p0AC3ePLJJ2Wz2Up8nP8f5v/7v//Txo0b9eWXX6py5cq69957ZYzx4Dtwn7L2jST98ccfuvHGG3XrrbfqgQce8FDk7udM3wC4NMnJydq6davmzZvn6VC8RuPGjbVp0yatWbNGDz/8sBITE/XTTz95OiyP27t3r0aMGKF33nnH7f+PZqRUCWrWrKnKlSsrPT3doT09PV2RkZFFviYyMrJM+6NkzlyDAv/85z81YcIELVmyxH2TslVwZe3/nTt3avfu3erTp4+9LT8/X5Lk4+Oj7du3q0GDBu4NuoJx5ncgKipKVapUUeXKle1tTZs21cGDB3Xq1Cn5+vq6NeaKxJn+//vf/6577rlH999/vySpZcuWys7O1tChQ/XUU0+pUiU+D3K34u7FwcHBjJJyg8cee0yDBw8ucZ/69evb/12zZk3VrFlTjRo1UtOmTRUTE6PVq1dXyK9LlLVv9u/fry5duui6667T66+/7uboPKusfYNLy4uBYcOG6eOPP9bXX3+t2rVrezocr+Hr66srr7xSkhQXF6e1a9fqlVde0cyZMz0cmWetX79ehw4d0lVXXWVvO3PmjL7++mu9+uqrys3Ndfi/xqWgKFUCX19fxcXFaenSpfYl7fPz87V06VINGzasyNfEx8dr6dKlDkslLl68uEImWlZw5hpI0sSJEzVu3Dh98cUXuvrqqy2KtuIpa/83adJEP/zwg0Pb008/raysLL3yyiuKiYmxIuwKxZnfgeuvv15z585Vfn6+vQDyyy+/KCoqioJUGTnT/ydOnChUeCq4aVfU0SDeJj4+Xp9++qlDG/di9wkPD1d4eLhTry344CI3N9eVIXmNsvTNH3/8oS5duiguLk5paWkVvoB9KT83lytn82Jc3owxGj58uBYuXKjly5erXr16ng7Jq+Xn51fYe1JZdOvWrdD/64YMGaImTZroiSeecFlBShKr713MvHnzjJ+fn5k9e7b56aefzNChQ01oaKg5ePCgMcaYe+65xzz55JP2/b/77jvj4+Nj/vnPf5qff/7ZpKSkmCpVqpgffvjBU2+h3CvrNZgwYYLx9fU177//vjlw4ID9kZWV5am3UK6Vtf8vxOp7l66s12DPnj0mKCjIDBs2zGzfvt18/PHHJiIiwrzwwgueegvlWln7PyUlxQQFBZl3333X/Pbbb+bLL780DRo0MLfddpun3kK5l5WVZTZu3Gg2btxoJJlJkyaZjRs3mt9//90YY8yTTz5p7rnnHvv+v/32m6latar5v//7P/Pzzz+badOmmcqVK5vPP//cU28BxpjVq1ebqVOnmo0bN5rdu3ebpUuXmuuuu840aNDA5OTkeDo8j9q3b5+58sorTbdu3cy+ffsc8hcY8/vvv5uNGzea1NRUExgYaP97cDnmdhe7J12uLnafuJw9/PDDJiQkxCxfvtzhb8uJEyc8HZrHPfnkk2bFihVm165dZsuWLebJJ580NpvNfPnll54OzSu5a/U9ilKlMHXqVFOnTh3j6+trrr32WrN69Wr7tk6dOpnExESH/d977z3TqFEj4+vra5o3b24++eQTiyOueMpyDWJjY42kQo+UlBTrA68gyvo7cD6KUq5R1muwcuVK07ZtW+Pn52fq169vxo0bZ06fPm1x1BVHWfo/Ly/PPPvss6ZBgwbG39/fxMTEmEceecQcPXrU+sAriGXLlhX5d72g3xMTE02nTp0KvaZNmzbG19fX1K9f36SlpVkeNxxt2bLFdOnSxYSFhRk/Pz9Tt25d89BDD5l9+/Z5OjSPS0tLK/JnnM+Pz0pMTCyyb5YtW+bp0DyipHvS5epi94nLWXF/W7gvGnPfffeZ2NhY4+vra8LDw023bt0oSJXAXUUpmzF8lwAAAAAAAADWqthfVgcAAAAAAIBXoigFAAAAAAAAy1GUAgAAAAAAgOUoSgEAAAAAAMByFKUAAAAAAABgOYpSAAAAAAAAsBxFKQAAAAAAAFiOohQAAAAAAAAsR1EKwGWrc+fOGjlypKfDAAAAKFfIoQC4CkUpAOVSnz59dOONNxa57ZtvvpHNZtOWLVssjgoAAMC7kUMB8CYUpQCUS0lJSVq8eLH27dtXaFtaWpquvvpqtWrVygORAQAAeC9yKADehKIUgHLppptuUnh4uGbPnu3Qfvz4cS1YsED9+vXToEGDdMUVV6hq1apq2bKl3n333RKPabPZtGjRIoe20NBQh3Ps3btXt912m0JDQxUWFqa+fftq9+7drnlTAAAAbkYOBcCbUJQCUC75+Pjo3nvv1ezZs2WMsbcvWLBAZ86c0d133624uDh98skn2rp1q4YOHap77rlH33//vdPnzMvLU0JCgoKCgvTNN9/ou+++U2BgoG688UadOnXKFW8LAADArcihAHgTilIAyq377rtPO3fu1IoVK+xtaWlpGjBggGJjYzVmzBi1adNG9evX1/Dhw3XjjTfqvffec/p88+fPV35+vv7973+rZcuWatq0qdLS0rRnzx4tX77cBe8IAADA/cihAHgLilIAyq0mTZrouuuu03/+8x9J0o4dO/TNN98oKSlJZ86c0fPPP6+WLVsqLCxMgYGB+uKLL7Rnzx6nz7d582bt2LFDQUFBCgwMVGBgoMLCwpSTk6OdO3e66m0BAAC4FTkUAG/h4+kAAOBSJCUlafjw4Zo2bZrS0tLUoEEDderUSS+99JJeeeUVTZkyRS1btlS1atU0cuTIEoeI22w2h2Hs0tnh5gWOHz+uuLg4vfPOO4VeGx4e7ro3BQAA4GbkUAC8AUUpAOXabbfdphEjRmju3Ll688039fDDD8tms+m7775T3759dffdd0uS8vPz9csvv6hZs2bFHis8PFwHDhywP//111914sQJ+/OrrrpK8+fPV0REhIKDg933pgAAANyMHAqAN+DrewDKtcDAQN1+++0aO3asDhw4oMGDB0uSGjZsqMWLF2vlypX6+eef9eCDDyo9Pb3EY3Xt2lWvvvqqNm7cqHXr1umhhx5SlSpV7Nvvuusu1axZU3379tU333yjXbt2afny5Xr00UeLXFYZAADAW5FDAfAGFKUAlHtJSUk6evSoEhISFB0dLUl6+umnddVVVykhIUGdO3dWZGSk+vXrV+JxXn75ZcXExKhDhw668847NWbMGFWtWtW+vWrVqvr6669Vp04d3XLLLWratKmSkpKUk5PDp34AAKDcIYcC4Gk2c+GXfwEAAAAAAAA3Y6QUAAAAAAAALEdRCgAAAAAAAJajKAUAAAAAAADLUZQCAAAAAACA5ShKAQAAAAAAwHIUpQAAAAAAAGA5ilIAAAAAAACwHEUpAAAAAAAAWI6iFAAAAAAAACxHUQoAAAAAAACWoygFAAAAAAAAy1GUAgAAAAAAgOX+PzTw703acaoqAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1200x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import torch\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# 创建数据\n",
    "rand = torch.rand(1000)  # 1000个服从[0,1]均匀分布的随机数\n",
    "randn = torch.randn(1000)  # 1000个服从标准正态分布的随机数\n",
    "\n",
    "# 可视化均匀分布，画布大小\n",
    "plt.figure(figsize=(12, 5))\n",
    "\n",
    "# 将画布分为1行2列，当前选择第1列\n",
    "plt.subplot(1, 2, 1)\n",
    "plt.hist(rand.numpy(), bins=50, color='blue')\n",
    "plt.title('Uniform Distribution [0,1]')\n",
    "plt.xlabel('Value')\n",
    "plt.ylabel('Frequency')\n",
    "\n",
    "# 可视化标准正态分布\n",
    "# 将画布分为1行2列，当前选择第2列\n",
    "plt.subplot(1, 2, 2)\n",
    "plt.hist(randn.numpy(), bins=50, color='green')\n",
    "plt.title('Standard Normal Distribution')\n",
    "plt.xlabel('Value')\n",
    "plt.ylabel('Frequency')\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b75de2b880a12c80",
   "metadata": {},
   "source": [
    "## 张量属性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "89419379e8887c30",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([2, 3])\n",
      "torch.float32\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "x = torch.rand(2, 3)\n",
    "\n",
    "print(x.shape)  # 张量形状\n",
    "print(x.dtype)  # 数据类型\n",
    "print(x.device)  # 存储设备(CPU/GPU)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ab507076a41a96a5",
   "metadata": {},
   "source": [
    "## 张量运算"
   ]
  },
  {
   "cell_type": "code",
   "id": "7eb681aad22dbe3",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T04:29:27.425242Z",
     "start_time": "2025-06-15T04:29:27.416260Z"
    }
   },
   "source": [
    "x = torch.tensor([1, 2, 3])\n",
    "y = torch.tensor([4, 5, 6])\n",
    "\n",
    "# 基本运算\n",
    "print(x + y)  # 加法\n",
    "print(x * y)  # 乘法\n",
    "\n",
    "print(x @ y)  # 点积, 对应元素相乘后相加\n",
    "print(torch.dot(x, y))  # 点积"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([5, 7, 9])\n",
      "tensor([ 4, 10, 18])\n",
      "tensor(32)\n",
      "tensor(32)\n"
     ]
    }
   ],
   "execution_count": 21
  },
  {
   "cell_type": "code",
   "id": "d675188d65305006",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T04:22:04.433541Z",
     "start_time": "2025-06-15T04:22:04.416842Z"
    }
   },
   "source": [
    "# 矩阵乘法\n",
    "import torch\n",
    "\n",
    "A = torch.tensor([[1, 2],\n",
    "                  [3, 4]])\n",
    "\n",
    "B = torch.tensor([[5, 6],\n",
    "                  [7, 8]])\n",
    "\n",
    "# 矩阵乘法的三种写法\n",
    "print(A @ B)\n",
    "print(torch.mm(A, B))  # 只支持二维矩阵\n",
    "print(torch.matmul(A, B))  # 支持多维矩阵\n",
    "\n",
    "# 矩阵不能点积，矩阵只有点乘\n",
    "print(torch.dot(A, B))"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[19, 22],\n",
      "        [43, 50]])\n",
      "tensor([[19, 22],\n",
      "        [43, 50]])\n",
      "tensor([[19, 22],\n",
      "        [43, 50]])\n"
     ]
    },
    {
     "ename": "RuntimeError",
     "evalue": "1D tensors expected, but got 2D and 2D tensors",
     "output_type": "error",
     "traceback": [
      "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[0;31mRuntimeError\u001B[0m                              Traceback (most recent call last)",
      "Cell \u001B[0;32mIn[17], line 16\u001B[0m\n\u001B[1;32m     13\u001B[0m \u001B[38;5;28mprint\u001B[39m(torch\u001B[38;5;241m.\u001B[39mmatmul(A, B))  \u001B[38;5;66;03m# 支持多维矩阵\u001B[39;00m\n\u001B[1;32m     15\u001B[0m \u001B[38;5;66;03m# 矩阵不能点积，矩阵只有点乘\u001B[39;00m\n\u001B[0;32m---> 16\u001B[0m \u001B[38;5;28mprint\u001B[39m(\u001B[43mtorch\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdot\u001B[49m\u001B[43m(\u001B[49m\u001B[43mA\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mB\u001B[49m\u001B[43m)\u001B[49m)\n",
      "\u001B[0;31mRuntimeError\u001B[0m: 1D tensors expected, but got 2D and 2D tensors"
     ]
    }
   ],
   "execution_count": 17
  },
  {
   "cell_type": "code",
   "id": "b7ea6575cd8be0fe",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T04:27:08.785297Z",
     "start_time": "2025-06-15T04:27:08.780188Z"
    }
   },
   "source": [
    "# 矩阵点乘，对应元素相乘\n",
    "import torch\n",
    "\n",
    "A = torch.tensor([[1, 2],\n",
    "                  [3, 4]])\n",
    "\n",
    "B = torch.tensor([[5, 6],\n",
    "                  [7, 8]])\n",
    "\n",
    "# 矩阵点乘的两种写法\n",
    "print(A * B)\n",
    "print(torch.mul(A, B))"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[ 5, 12],\n",
      "        [21, 32]])\n",
      "tensor([[ 5, 12],\n",
      "        [21, 32]])\n"
     ]
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T04:47:26.423770Z",
     "start_time": "2025-06-15T04:47:26.415164Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 形状是(2, 2, 3)\n",
    "A = torch.tensor([\n",
    "    [[1, 2, 3], [4, 5, 6]],\n",
    "    [[7, 8, 9], [10, 11, 12]]\n",
    "])\n",
    "\n",
    "A"
   ],
   "id": "f1ff1cb1a3b898f6",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "tensor([[[ 1,  2,  3],\n",
       "         [ 4,  5,  6]],\n",
       "\n",
       "        [[ 7,  8,  9],\n",
       "         [10, 11, 12]]])"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 37
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T04:49:39.152104Z",
     "start_time": "2025-06-15T04:49:39.147136Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 转置\n",
    "A.transpose(1, 2).shape"
   ],
   "id": "8df2a56315a8cd6b",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2, 3, 2])"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 39
  },
  {
   "cell_type": "code",
   "id": "72de5420a46e8d82",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T04:53:17.203145Z",
     "start_time": "2025-06-15T04:53:17.195702Z"
    }
   },
   "source": [
    "# 矩阵批量乘法，一定要三维，第0维是batch_size\n",
    "import torch\n",
    "\n",
    "# 形状是(2, 2, 3)\n",
    "A = torch.tensor([\n",
    "    [[1, 2, 3], [4, 5, 6]],\n",
    "    [[1, 2, 3], [4, 5, 6]]\n",
    "])\n",
    "\n",
    "# 形状是(2, 2, 3)\n",
    "B = torch.tensor([\n",
    "    [[7, 8, 9], [10, 11, 12]],\n",
    "    [[8, 8, 9], [10, 11, 12]]\n",
    "])\n",
    "\n",
    "# 本质上就是矩阵乘法，所以需要改变维度, batch\n",
    "print(torch.bmm(A, B.transpose(1, 2)))\n",
    "\n",
    "print(torch.matmul(A, B.transpose(1, 2)))\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[[ 50,  68],\n",
      "         [122, 167]],\n",
      "\n",
      "        [[ 51,  68],\n",
      "         [126, 167]]])\n",
      "tensor([[[ 50,  68],\n",
      "         [122, 167]],\n",
      "\n",
      "        [[ 51,  68],\n",
      "         [126, 167]]])\n"
     ]
    }
   ],
   "execution_count": 42
  },
  {
   "cell_type": "markdown",
   "id": "bbc5a77d1e229567",
   "metadata": {},
   "source": [
    "## 自动微分 (Autograd)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "da1a6a2612c0cad5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-05-13T12:44:23.833144Z",
     "start_time": "2025-05-13T12:44:23.819992Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor(7.)\n"
     ]
    }
   ],
   "source": [
    "# 创建需要梯度的张量\n",
    "x = torch.tensor(2.0, requires_grad=True)\n",
    "\n",
    "# 定义计算\n",
    "y = x ** 2 + 3 * x + 1\n",
    "\n",
    "# 计算梯度\n",
    "y.backward()\n",
    "\n",
    "# 查看梯度\n",
    "print(x.grad)  # dy/dx = 2x + 3 = 7 (当x=2时)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc62160857019080",
   "metadata": {},
   "source": [
    "## Dataset和DataLoader\n",
    "\n",
    "Dataset和DataLoader是PyTorch中用于处理数据集和数据加载的组件。\n",
    "\n",
    "Dataset类是一个抽象类，用于定义数据集的接口。DataLoader类是一个迭代器，用于加载数据集。"
   ]
  },
  {
   "cell_type": "code",
   "id": "b726597e2b87421",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T10:43:57.721850Z",
     "start_time": "2025-06-15T10:43:57.715234Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset, DataLoader\n",
    "\n",
    "\n",
    "# 自定义数据集类（继承Dataset）\n",
    "class ZhouyuDataset(Dataset):\n",
    "\n",
    "    def __init__(self, data):\n",
    "        self.data = data\n",
    "\n",
    "    def __len__(self):\n",
    "        \"\"\"返回数据集大小（必须实现）\"\"\"\n",
    "        return len(self.data)\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        \"\"\"访问单个样本（必须实现），index是索引位置\"\"\"\n",
    "        # return [i + 1 for i in self.data[index]]\n",
    "        return torch.tensor(self.data[index]) + 1"
   ],
   "outputs": [],
   "execution_count": 90
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T10:50:05.604806Z",
     "start_time": "2025-06-15T10:50:05.590648Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 创建数据集实例，比如我们的训练样本\n",
    "data = list(range(1, 11))\n",
    "print(data)\n",
    "\n",
    "dataset = ZhouyuDataset(data)\n",
    "\n",
    "# 查看前5个样本\n",
    "dataset[:5]"
   ],
   "id": "58842ad35c9eb461",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "tensor([2, 3, 4, 5, 6])"
      ]
     },
     "execution_count": 101,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 101
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T10:50:08.161070Z",
     "start_time": "2025-06-15T10:50:08.155076Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 创建数据加载器DataLoader\n",
    "dataloader = DataLoader(\n",
    "    dataset,\n",
    "    batch_size=3,\n",
    "    shuffle=True  # 是否打乱数据\n",
    ")\n",
    "\n",
    "# 批量加载数据\n",
    "for batch in dataloader:\n",
    "    # 每个batch是形状为[batch_size]的张量\n",
    "    print(\"Batch:\", batch)"
   ],
   "id": "c2cda85e4ab830ab",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Batch: tensor([3, 8, 6])\n",
      "Batch: tensor([ 5,  9, 11])\n",
      "Batch: tensor([2, 4, 7])\n",
      "Batch: tensor([10])\n"
     ]
    }
   ],
   "execution_count": 102
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T10:50:14.490550Z",
     "start_time": "2025-06-15T10:50:14.483409Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def custom_collate_fn(batch):\n",
    "    print(1)\n",
    "    return [item * 10 for item in batch]\n",
    "\n",
    "# 创建数据加载器DataLoader\n",
    "dataloader = DataLoader(\n",
    "    dataset,\n",
    "    batch_size=3,\n",
    "    collate_fn=custom_collate_fn,\n",
    "    shuffle=True  # 是否打乱数据\n",
    ")\n",
    "\n",
    "# 批量加载数据\n",
    "for batch in dataloader:\n",
    "    # 每个batch是形状为[batch_size]的张量\n",
    "    print(\"Batch:\", batch)"
   ],
   "id": "2dc025f0501cd788",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n",
      "Batch: [tensor(60), tensor(90), tensor(20)]\n",
      "1\n",
      "Batch: [tensor(100), tensor(70), tensor(30)]\n",
      "1\n",
      "Batch: [tensor(80), tensor(40), tensor(50)]\n",
      "1\n",
      "Batch: [tensor(110)]\n"
     ]
    }
   ],
   "execution_count": 103
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T10:56:58.567926Z",
     "start_time": "2025-06-15T10:56:58.562991Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import Dataset, DataLoader\n",
    "\n",
    "\n",
    "# 自定义数据集类（继承Dataset）\n",
    "class TupleDataset(Dataset):\n",
    "    def __init__(self, start, end):\n",
    "        \"\"\"初始化数据集：存储从start到end的整数\"\"\"\n",
    "        self.data = list(range(start, end))\n",
    "\n",
    "    def __len__(self):\n",
    "        \"\"\"返回数据集大小（必须实现）\"\"\"\n",
    "        return len(self.data)\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        \"\"\"访问单个样本（必须实现），index是索引位置\"\"\"\n",
    "        return f\"x{self.data[index]}\", f\"y{self.data[index]}\", f\"z{self.data[index]}\""
   ],
   "id": "d1c076dca413d09e",
   "outputs": [],
   "execution_count": 116
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-06-15T10:57:18.039567Z",
     "start_time": "2025-06-15T10:57:18.032991Z"
    }
   },
   "cell_type": "code",
   "source": [
    "dataset = TupleDataset(start=1, end=10)\n",
    "\n",
    "# 创建数据加载器DataLoader\n",
    "dataloader = DataLoader(\n",
    "    dataset,\n",
    "    batch_size=3,\n",
    "    # shuffle=True  # 是否打乱数据\n",
    ")\n",
    "\n",
    "# 批量加载数据\n",
    "for batch in dataloader:\n",
    "    # 每个batch是形状为[batch_size]的张量\n",
    "    print(\"Batch:\", batch)\n"
   ],
   "id": "ef94ca395eec0a3e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Batch: [('x1', 'x2', 'x3'), ('y1', 'y2', 'y3'), ('z1', 'z2', 'z3')]\n",
      "Batch: [('x4', 'x5', 'x6'), ('y4', 'y5', 'y6'), ('z4', 'z5', 'z6')]\n",
      "Batch: [('x7', 'x8', 'x9'), ('y7', 'y8', 'y9'), ('z7', 'z8', 'z9')]\n"
     ]
    }
   ],
   "execution_count": 117
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
